package git.soulbgm.utils;

import ch.qos.logback.core.util.FileUtil;
import cn.afterturn.easypoi.entity.ImageEntity;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.TemplateExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.handler.inter.IExcelDictHandler;
import cn.afterturn.easypoi.util.PoiMergeCellUtil;
import cn.afterturn.easypoi.word.WordExportUtil;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

/**
 * EasyPOI工具类
 *
 * @author SoulBGM
 * @date 2022/03/24 13:51
 */
public class EasyPoiUtil {

    public static final String XLSX = ".xlsx";

    public static <T> void exportExcel(HttpServletResponse response, String excelName, List<T> list, Class<T> clazz) throws IOException {
        exportExcel(response, excelName, list, clazz, null);
    }

    public static <T> void exportExcel(HttpServletResponse response, String excelName, List<T> list, Class<T> clazz, IExcelDictHandler dictHandler) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        String fileName = excelName + "_" + System.currentTimeMillis();
        response.setHeader("Content-Disposition", String.format("attachment; filename=%s.xlsx", new String(fileName.getBytes("gb2312"), "ISO8859-1")));
        if (list.size() > 0) {
            try (ServletOutputStream out = response.getOutputStream()) {
                ExportParams params = new ExportParams(null, "Sheet1", ExcelType.XSSF);
                if (dictHandler != null) {
                    params.setDictHandler(dictHandler);
                }
                modelWriterExcel(out, list, clazz, params);
            }
        }
    }

    /**
     * 创建序列
     *
     * @param size 序列长度
     * @return 序列列表
     */
    public static List<Integer> createSequence(Integer size) {
        List<Integer> sequenceList = new ArrayList<>(size != null ? size : 0);
        if (size != null) {
            for (Integer i = 0; i < size; i++) {
                sequenceList.add(i + 1);
            }
        }
        return sequenceList;
    }

    /**
     * 生成val为null的mergeMap
     *
     * @param columns 需要处理的列
     * @return mergeMap
     */
    public static Map<Integer, int[]> createMergeMap(Integer... columns) {
        Map<Integer, int[]> mergeMap = new HashMap<>(columns.length);
        for (Integer column : columns) {
            mergeMap.put(column, null);
        }
        return mergeMap;
    }

    /**
     * 循环 纵向合并相同内容的单元格
     *
     * @param cycleNum      循环次数
     * @param sheet         Sheet类
     * @param columns       需要处理的列
     * @param firstStartRow 第一次开始循环行
     * @param firstEndRow   第一次结束循环行
     */
    public static void cycleMergeCells(int cycleNum, Sheet sheet, int firstStartRow, int firstEndRow, Integer... columns) {
        cycleMergeCells(cycleNum, sheet, firstStartRow, firstEndRow, createMergeMap(columns));
    }

    /**
     * 循环 纵向合并相同内容的单元格
     *
     * @param cycleNum      循环次数
     * @param sheet         Sheet类
     * @param mergeMap      需要处理的列
     * @param firstStartRow 第一次开始循环行
     * @param firstEndRow   第一次结束循环行
     */
    public static void cycleMergeCells(int cycleNum, Sheet sheet, int firstStartRow, int firstEndRow, Map<Integer, int[]> mergeMap) {
        int step = (firstEndRow - firstStartRow) + 1;
        for (int i = 0; i < cycleNum; i++) {
            int cycleStartRow = firstStartRow + i * step;
            int cycleEndRow = firstEndRow + i * step;
            PoiMergeCellUtil.mergeCells(sheet, mergeMap, cycleStartRow, cycleEndRow);
        }
    }

    /**
     * 写Excel
     * 实体类中必须有easypoi的注解
     * = @ExcelTarget       限定一个到处实体的注解,以及一些通用设置,作用于最外面的实体
     * = @Excel             这个是必须使用的注解,如果需求简单只使用这一个注解也是可以的,涵盖了常用的Excel需求,需要大家熟悉这个功能,主要分为基础,图片处理,时间处理,合并处理几块
     * = @ExcelEntity       标记是不是导出excel 标记为实体类,一遍是一个内部属性类,标记是否继续穿透,可以自定义内部id
     * = @ExcelCollection   一对多的集合注解,用以标记集合是否被数据以及集合的整体排序
     *
     * @param filePath 输出地址
     * @param dataList 数据
     * @param clazz    数据的Class
     * @return 成功true 失败false
     */
    public static <T> boolean modelWriterExcel(String filePath, List<T> dataList, Class<T> clazz) {
        return modelWriterExcel(filePath, dataList, clazz, null, null);
    }

    /**
     * 写Excel
     * 实体类中必须有easypoi的注解
     * = @ExcelTarget       限定一个到处实体的注解,以及一些通用设置,作用于最外面的实体
     * = @Excel             这个是必须使用的注解,如果需求简单只使用这一个注解也是可以的,涵盖了常用的Excel需求,需要大家熟悉这个功能,主要分为基础,图片处理,时间处理,合并处理几块
     * = @ExcelEntity       标记是不是导出excel 标记为实体类,一遍是一个内部属性类,标记是否继续穿透,可以自定义内部id
     * = @ExcelCollection   一对多的集合注解,用以标记集合是否被数据以及集合的整体排序
     *
     * @param filePath 输出地址
     * @param dataList 数据
     * @param clazz    数据的Class
     * @return 成功true 失败false
     */
    public static <T> boolean modelWriterExcel(String filePath, List<T> dataList, Class<T> clazz, Class<?> styleClass, WorkbookHandler handler) {
        try {
            return modelWriterExcel(new FileOutputStream(filePath), dataList, clazz, styleClass, handler);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 写Excel
     * 实体类中必须有easypoi的注解
     * = @ExcelTarget       限定一个到处实体的注解,以及一些通用设置,作用于最外面的实体
     * = @Excel             这个是必须使用的注解,如果需求简单只使用这一个注解也是可以的,涵盖了常用的Excel需求,需要大家熟悉这个功能,主要分为基础,图片处理,时间处理,合并处理几块
     * = @ExcelEntity       标记是不是导出excel 标记为实体类,一遍是一个内部属性类,标记是否继续穿透,可以自定义内部id
     * = @ExcelCollection   一对多的集合注解,用以标记集合是否被数据以及集合的整体排序
     *
     * @param out      输出流
     * @param dataList 数据
     * @param clazz    数据的Class
     * @return 成功true 失败false
     */
    public static <T> boolean modelWriterExcel(OutputStream out, List<T> dataList, Class<T> clazz, ExportParams params) {
        return modelWriterExcel(out, dataList, clazz, params, null);
    }

    public static <T> boolean modelWriterExcel(OutputStream out, List<T> dataList, Class<T> clazz, ExportParams params, WorkbookHandler handler) {
        Workbook workbook = ExcelExportUtil.exportExcel(params, clazz, dataList);
        if (handler != null) {
            handler.handle(workbook);
        }
        try {
            workbook.write(out);
            return true;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 写Excel
     * 实体类中必须有easypoi的注解
     * = @ExcelTarget       限定一个到处实体的注解,以及一些通用设置,作用于最外面的实体
     * = @Excel             这个是必须使用的注解,如果需求简单只使用这一个注解也是可以的,涵盖了常用的Excel需求,需要大家熟悉这个功能,主要分为基础,图片处理,时间处理,合并处理几块
     * = @ExcelEntity       标记是不是导出excel 标记为实体类,一遍是一个内部属性类,标记是否继续穿透,可以自定义内部id
     * = @ExcelCollection   一对多的集合注解,用以标记集合是否被数据以及集合的整体排序
     *
     * @param out        输出流
     * @param dataList   数据
     * @param clazz      数据的Class
     * @param styleClass 样式的Class
     * @param handler    进行后期处理的处理器
     * @return 成功true 失败false
     */
    public static <T> boolean modelWriterExcel(OutputStream out, List<T> dataList, Class<T> clazz, Class<?> styleClass, WorkbookHandler handler) {
        ExportParams params = new ExportParams(null, "Sheet1", ExcelType.XSSF);
        if (styleClass != null) {
            params.setStyle(styleClass);
        }
        return modelWriterExcel(out, dataList, clazz, params, handler);
    }

    /**
     * 使用模板写Excel
     *
     * @param path         excel存放地址
     * @param excelName    excel名称
     * @param data         数据
     * @param templatePath 模板地址
     * @return 成功true 失败false
     */
    public static <T> boolean templateWriterExcel(String path, String excelName, Map<String, Object> data, String templatePath) {
        return templateWriterExcel(path, excelName, data, templatePath, null);
    }

    /**
     * 使用模板写Excel
     *
     * @param path         excel存放地址
     * @param excelName    excel名称
     * @param data         数据
     * @param templatePath 模板地址
     * @param handler      进行后期处理的处理器
     * @return 成功true 失败false
     */
    public static <T> boolean templateWriterExcel(String path, String excelName, Map<String, Object> data, String templatePath, WorkbookHandler handler) {
        String filePath = path + File.separator + excelName + XLSX;
        File file = new File(filePath);
        FileUtil.createMissingParentDirectories(file);
        OutputStream out = null;
        try {
            out = new FileOutputStream(file);
            return templateWriterExcel(out, data, templatePath, handler);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 使用模板写Excel
     *
     * @param out          输出流
     * @param data         数据
     * @param templatePath 模板地址
     * @return 成功true 失败false
     */
    public static <T> boolean templateWriterExcel(OutputStream out, Map<String, Object> data, String templatePath) {
        return templateWriterExcel(out, data, templatePath, null);
    }

    /**
     * 使用模板写Excel
     *
     * @param out          输出流
     * @param data         数据
     * @param templatePath 模板地址
     * @param handler      进行后期处理的处理器
     * @return 成功true 失败false
     */
    public static <T> boolean templateWriterExcel(OutputStream out, Map<String, Object> data, String templatePath, WorkbookHandler handler) {
        if (out == null) {
            return false;
        }
        TemplateExportParams params = new TemplateExportParams(templatePath, true);
        Workbook book = null;
        try {
            // 简单模板导出方法
            book = ExcelExportUtil.exportExcel(params, data);
            if (handler != null) {
                handler.handle(book);
            }
            book.write(out);
            out.flush();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                out.close();
                if (book != null) {
                    book.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public interface WorkbookHandler {
        void handle(Workbook book);
    }

    /**
     * 使用模板写Word
     * 如果需要插入图片仅需在data中放入ImageEntity对象即可
     *
     * @param out          输出流
     * @param data         数据
     * @param templatePath 模板地址
     * @return 成功true 失败false
     */
    public static boolean templateWriterWord(OutputStream out, Map<String, Object> data, String templatePath) {
        if (out == null) {
            return false;
        }
        try {
            XWPFDocument doc = WordExportUtil.exportWord07(templatePath, data);
            doc.write(out);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 使用模板写Word
     * 插入多张图片
     *
     * @param out             输出流
     * @param data            数据
     * @param templatePath    模板地址
     * @param imageEntityList 多张图片集合
     * @return 成功true 失败false
     */
    public static boolean templateWriterWordImage(OutputStream out, Map<String, Object> data, String templatePath, List<ImageEntity> imageEntityList) {
        Map<String, List<ImageEntity>> multiplePictures = new HashMap<>(1);
        multiplePictures.put("one", imageEntityList);
        return templateWriterWordImage(out, data, templatePath, multiplePictures);
    }

    /**
     * 使用模板写Word
     * 插入多张图片
     *
     * @param out              输出流
     * @param data             数据
     * @param templatePath     模板地址
     * @param multiplePictures 多组图片集合
     * @return 成功true 失败false
     */
    public static boolean templateWriterWordImage(OutputStream out, Map<String, Object> data, String templatePath, Map<String, List<ImageEntity>> multiplePictures) {
        if (out == null) {
            return false;
        }
        Set<String> keySet = multiplePictures.keySet();

        Map<String, Object> imageEntityMap = new HashMap<>(keySet.size() * 3);
        for (String s : keySet) {
            List<ImageEntity> list = multiplePictures.get(s);
            if (list != null) {
                int size = list.size();
                List<String> placeholder = new ArrayList<>(size);
                for (int i = 0; i < size; i++) {
                    String key = s + i;
                    placeholder.add("{{" + key + "}}");
                    imageEntityMap.put(key, list.get(i));
                }
                data.put(s, placeholder);
            }
        }

        try {
            String tempFileName = "templateWriterWordImage_" + System.currentTimeMillis();
            File tempFile = File.createTempFile(tempFileName, ".docx");
            String tempFilePath = tempFile.getAbsolutePath();
            /*System.out.println(tempFilePath);*/
            FileOutputStream fos = new FileOutputStream(tempFile);
            templateWriterWord(fos, data, templatePath);
            boolean result = templateWriterWord(out, imageEntityMap, tempFilePath);
            // 最后删除临时文件
            tempFile.delete();
            return result;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

}
